﻿using MX.Core.Log;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace MX.Core.Data
{
    /// <summary>
    /// qqwry.dat文件的读取器
    /// <para>关于qqwry.dat文件的数据结构，可参考以下地址:http://www.jb51.net/article/17197.htm</para>
    /// </summary>
    public class IPWryReader : IDisposable
    {
        /// <summary>
        /// IP地址
        /// </summary>
        public class IPLocation
        {
            /// <summary>
            /// IP地址
            /// </summary>
            /// <param name="country"></param>
            /// <param name="area"></param>
            public IPLocation(string country, string area)
            {
                this.Country = country;
                this.Area = area;
            }
            /// <summary>
            /// 国家
            /// </summary>
            public String Country { get; private set; }
            /// <summary>
            /// 地区
            /// </summary>
            public String Area { get; private set; }
        }

        //文件头: 读取索引的开始位置和结束位置
        private long ipBegin, ipEnd;

        //记录区 重定向的两种模式   
        private const byte RedirectMode1 = 0x01;
        private const byte RedirectMode2 = 0x02;

        //索引区: 字节长度为7  其中[4B(IP)+3B(记录区地址)]     
        private const int IpRecordLength = 7;


        //数据库文件      
        private FileStream ipFile;
        private const string UnCountry = "未知国家";
        private const string UnArea = "未知地区";
        private Encoding charset;
        //存储文本内容 120字节缓冲区      
        private byte[] buffer;

        //存储4个字节       
        private byte[] b4;

        ILogger logger = Logger.GetLogger("IPWryReader");

        /// <summary>
        /// 根据ip库文件实例化
        /// </summary>
        /// <param name="ipfile">ip文件。也就是qqwry.dat的文件地址</param>
        public IPWryReader(string ipfile)
        {

            buffer = new byte[120];
            b4 = new byte[4];
            try
            {
                ipFile = new FileStream(ipfile, FileMode.Open, FileAccess.Read, FileShare.Read);

            }
            catch (Exception ex)
            {
                logger.Error(ex, "打开IP数据库[{0}]时出错", ipfile);
                throw new ArgumentNullException("打开IP数据库文件出错！");
            }
            ipBegin = ReadLong4(0);
            ipEnd = ReadLong4(4);
            charset = Encoding.GetEncoding("gbk");
        }

        /// <summary>
        /// 搜索某个IP
        /// </summary>
        /// <param name="ip">ip地址</param>
        /// <returns></returns>
        public IPLocation Search(string ip)
        {
            //将字符IP转换为字节
            string[] ipSp = ip.Split('.');
            if (ipSp.Length != 4)
            {
                return new IPLocation(UnCountry, UnArea);
            }


            IPLocation local = null;
            try
            {
                byte[] IP = new byte[4];
                for (int i = 0; i < IP.Length; i++)
                {
                    IP[i] = (byte)(int.Parse(ipSp[i]) & 0xFF);
                }

                long offset = LocateIP(IP); //获取

                if (offset != -1)
                {
                    local = GetIpLocation(offset);
                }
            }
            catch (Exception ex)
            {
                logger.Error(ex, "搜索IP[{0}]的地址时出错", ip);
            }

            return local ?? (new IPLocation(UnCountry, UnArea));
        }


        /// <summary>
        /// 取得具体信息
        /// </summary>
        /// <param name="offset"></param>
        /// <returns></returns>
        private IPLocation GetIpLocation(long offset)
        {
            ipFile.Position = offset + 4;
            //读取第一个字节判断是否是标志字节
            byte one = (byte)ipFile.ReadByte();
            string country, area;
            if (one == RedirectMode1)
            {
                //第一种模式
                //读取国家偏移
                long countryOffset = ReadLong3();
                //转至偏移处
                ipFile.Position = countryOffset;
                //再次检查标志字节
                byte b = (byte)ipFile.ReadByte();
                if (b == RedirectMode2)
                {
                    country = ReadString(ReadLong3());
                    ipFile.Position = countryOffset + 4;
                }
                else
                    country = ReadString(countryOffset);

                //读取地区标志
                area = ReadArea(ipFile.Position);

            }
            else if (one == RedirectMode2)
            {
                //第二种模式
                country = ReadString(ReadLong3());
                area = ReadArea(offset + 8);
            }
            else
            {
                //普通模式
                country = ReadString(--ipFile.Position);
                area = ReadString(ipFile.Position);
            }
            return new IPLocation(country, area);
        }


        /// <summary>
        /// 取得地区信息
        /// </summary>
        /// <param name="offset"></param>
        /// <returns></returns>
        private string ReadArea(long offset)
        {
            ipFile.Position = offset;
            byte one = (byte)ipFile.ReadByte();
            if (one == RedirectMode1 || one == RedirectMode2)
            {
                long areaOffset = ReadLong3(offset + 1);
                if (areaOffset == 0)
                    return UnArea;
                else
                {
                    return ReadString(areaOffset);
                }
            }
            else
            {
                return ReadString(offset);
            }
        }


        /// <summary>
        /// 读取字符串
        /// </summary>
        /// <param name="offset"></param>
        /// <returns></returns>
        private string ReadString(long offset)
        {
            ipFile.Position = offset;
            int i = 0;

            do
            {
                buffer[i] = (byte)ipFile.ReadByte();
            } while (buffer[i++] != (byte)(0));

            i--;
            if (i > 0)
                return charset.GetString(buffer, 0, i);
            else
                return "";
        }


        /// <summary>
        /// 查找IP地址所在的绝对偏移量 
        /// </summary>
        /// <param name="ip"></param>
        /// <returns></returns>
        private long LocateIP(byte[] ip)
        {
            long m = 0;
            int r;

            //比较第一个IP项
            ReadIP(ipBegin, b4);
            r = CompareIP(ip, b4);
            if (r == 0)
                return ipBegin;
            else if (r < 0)
                return -1;
            //开始二分搜索
            for (long i = ipBegin, j = ipEnd; i < j;)
            {
                m = this.GetMiddleOffset(i, j);
                ReadIP(m, b4);
                r = CompareIP(ip, b4);
                if (r > 0)
                    i = m;
                else if (r < 0)
                {
                    if (m == j)
                    {
                        j -= IpRecordLength;
                        m = j;
                    }
                    else
                    {
                        j = m;
                    }
                }
                else
                    return ReadLong3(m + 4);
            }
            m = ReadLong3(m + 4);
            ReadIP(m, b4);
            r = CompareIP(ip, b4);
            if (r <= 0)
                return m;
            else
                return -1;
        }

        /// <summary>
        /// 取得begin和end之间的偏移量  用于二分搜索
        /// </summary>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        private long GetMiddleOffset(long begin, long end)
        {
            long records = (end - begin) / IpRecordLength;
            records >>= 1;
            if (records == 0)
                records = 1;
            return begin + records * IpRecordLength;
        }


        /// <summary>
        /// 读出4字节的IP地址  调换字节顺序
        /// </summary>
        /// <param name="offset"></param>
        /// <param name="ip"></param>
        private void ReadIP(long offset, byte[] ip)
        {
            ipFile.Position = offset;
            ipFile.Read(ip, 0, ip.Length);
            byte tmp = ip[0];
            ip[0] = ip[3];
            ip[3] = tmp;
            tmp = ip[1];
            ip[1] = ip[2];
            ip[2] = tmp;
        }


        /// <summary>
        /// 比较IP地址是否相同
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="beginIP"></param>
        /// <returns></returns>
        private int CompareIP(byte[] ip, byte[] beginIP)
        {
            for (int i = 0; i < 4; i++)
            {
                int r = CompareByte(ip[i], beginIP[i]);
                if (r != 0)
                    return r;
            }
            return 0;
        }


        /// <summary>
        /// 比较两个字节是否相等  
        /// </summary>
        /// <param name="bsrc"></param>
        /// <param name="bdst"></param>
        /// <returns></returns>
        private int CompareByte(byte bsrc, byte bdst)
        {
            if ((bsrc & 0xFF) > (bdst & 0xFF))
                return 1;
            else if ((bsrc ^ bdst) == 0)
                return 0;
            else
                return -1;
        }

        /// <summary>
        /// 字节序为little-endian即低位在低地址
        /// 从绝对位置 读取4字节     
        /// </summary>
        /// <param name="offset"></param>
        /// <returns></returns>
        private long ReadLong4(long offset)
        {
            long ret = 0;
            ipFile.Position = offset;
            ret |= (ipFile.ReadByte() & 0xFF);
            ret |= ((ipFile.ReadByte() << 8) & 0xFF00);
            ret |= ((ipFile.ReadByte() << 16) & 0xFF0000);
            ret |= ((ipFile.ReadByte() << 24) & 0xFF000000);
            return ret;
        }


        /// <summary>
        /// 从绝对位置 读取3字节    
        /// </summary>
        /// <param name="offset"></param>
        /// <returns></returns>
        private long ReadLong3(long offset)
        {
            long ret = 0;
            ipFile.Position = offset;
            ret |= (ipFile.ReadByte() & 0xFF);
            ret |= ((ipFile.ReadByte() << 8) & 0xFF00);
            ret |= ((ipFile.ReadByte() << 16) & 0xFF0000);
            return ret;
        }

        /// <summary>
        /// 从当前位置读取3字节  
        /// </summary>
        /// <returns></returns>
        private long ReadLong3()
        {
            long ret = 0;
            ret |= (ipFile.ReadByte() & 0xFF);
            ret |= ((ipFile.ReadByte() << 8) & 0xFF00);
            ret |= ((ipFile.ReadByte() << 16) & 0xFF0000);
            return ret;
        }


        void IDisposable.Dispose()
        {
            if (this.ipFile != null)
            {
                this.ipFile.Dispose();
            }
        }
    }

}
